# python/server.py
from fastapi.middleware.cors import CORSMiddleware
from fastapi import FastAPI, UploadFile, Form, File
from fastapi.responses import JSONResponse, StreamingResponse
import shutil, os, time
from 水表普查数据 import format_survey_files
from 通过水表表号导入 import process_meter_files
from 对比相同表头差异 import filter_excel_by_column
from 对比相同表头同步备注信息 import sync_remarks
from 根据表头细分数据 import split_excel_by_header
from 抽查普表清单 import sample_meter_list
from 对比相同表头筛选 import filter_by_common_values
from 对比表头数据一致性 import compare_header_data_consistency
from 合并表格 import merge_excel_files_regular
from 预算 import process_multiple_budgets
import pandas as pd
import numpy as np
import urllib.parse
import json

app = FastAPI()
input_dir = "input"
output_dir = "output"
os.makedirs(input_dir, exist_ok=True)
os.makedirs(output_dir, exist_ok=True)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:8080", "http://127.0.0.1:8080"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


# ---------- 工具方法 ----------
def success_response(data=None, message="操作成功"):
    return {"status": "success", "message": message, "data": data}


def error_response(message="操作失败", status_code=400):
    return JSONResponse(
        status_code=status_code,
        content={"status": "error", "message": message, "data": None},
    )


def clear_folder(folder):
    if not os.path.exists(folder):
        os.makedirs(folder)
        return
    for f in os.listdir(folder):
        path = os.path.join(folder, f)
        try:
            if os.path.isfile(path):
                os.remove(path)
            elif os.path.isdir(path):
                shutil.rmtree(path)
        except PermissionError:
            raise ValueError(f"文件 {path} 正在被占用，请先关闭 Excel 文件再试。")


def safe_save_excel(wb, path, retry=3, delay=0.5):
    """保存 Excel，避免被占用导致失败"""
    for i in range(retry):
        try:
            if os.path.exists(path):
                os.remove(path)
            wb.save(path)
            return
        except PermissionError:
            if i < retry - 1:
                time.sleep(delay)
            else:
                raise ValueError(
                    f"无法保存文件 {path}，可能被占用，请先关闭 Excel 文件再试。"
                )


# ---------- 新增接口 ----------


@app.get("/c_hello")
async def health_check(asker: str = None):
    """健康检查接口，用于备份脚本等服务状态检测"""
    return {
        "status": "success", 
        "message": "服务正常运行", 
        "asker": asker,
        "timestamp": time.time()
    }


@app.post("/budget_calculation")
async def budget_calculation_endpoint(
    files: list[UploadFile], options: str = Form(None)
):
    """
    预算计算接口
    options JSON 示例：
    {
        "target_amount": 3700,
        "users": [
            {"name": "用户1", "address": "地址1"},
            {"name": "用户2", "address": "地址2"}
        ]
    }
    """
    try:
        # 清空文件夹
        clear_folder(input_dir)
        clear_folder(output_dir)

        # 保存上传文件
        for file in files:
            file_path = os.path.join(input_dir, file.filename)
            with open(file_path, "wb") as f:
                shutil.copyfileobj(file.file, f)

        # 解析 options
        opts = {}
        if options:
            try:
                opts = json.loads(options)
            except Exception as e:
                return error_response(f"解析 options 失败：{e}", status_code=400)

        target_amount = opts.get("target_amount", 3700)
        users = opts.get("users", [])

        if not users:
            return error_response("必须提供用户列表", status_code=400)

        # 获取模板文件
        template_files = [
            f for f in os.listdir(input_dir) if f.endswith((".xlsx", ".xls"))
        ]
        if not template_files:
            return error_response("未找到模板文件", status_code=400)

        template_file = os.path.join(input_dir, template_files[0])

        # 处理预算
        results = process_multiple_budgets(
            template_file, users, output_dir, target_amount
        )

        output_files = [os.path.basename(result["file_path"]) for result in results]

        return success_response(
            data=output_files,
            message=f"预算计算完成，共生成 {len(output_files)} 个文件",
        )

    except ValueError as e:
        return error_response(str(e), status_code=400)
    except Exception as e:
        return error_response(f"服务器内部错误：{str(e)}", status_code=500)


@app.post("/merge_excel_files")
async def merge_excel_files_endpoint(
    files: list[UploadFile], options: str = Form(None)
):
    """
    合并Excel文件接口
    options JSON 示例：
    {
        "output_filename": "合并文件.xlsx"
    }
    """
    try:
        # 清空文件夹
        clear_folder(input_dir)
        clear_folder(output_dir)

        # 保存上传文件
        for file in files:
            file_path = os.path.join(input_dir, file.filename)
            with open(file_path, "wb") as f:
                shutil.copyfileobj(file.file, f)

        # 解析 options
        opts = {}
        if options:
            try:
                opts = json.loads(options)
            except Exception as e:
                return error_response(f"解析 options 失败：{e}", status_code=400)

        output_filename = opts.get("output_filename", "合并文件.xlsx")

        # 调用合并函数
        success = merge_excel_files_regular(input_dir, output_filename)

        if success:
            output_files = [f for f in os.listdir(output_dir) if f == output_filename]
            return success_response(data=output_files, message="文件合并完成")
        else:
            return error_response("文件合并失败")

    except ValueError as e:
        return error_response(str(e), status_code=400)
    except Exception as e:
        return error_response(f"服务器内部错误：{str(e)}", status_code=500)


@app.post("/sample_data")
async def sample_data_endpoint(files: list[UploadFile], options: str = Form(None)):
    """
    通用数据抽样接口
    options JSON 示例：
    {
        "header_name": "抄表员",
        "filter_values": "张三,李四",
        "total_count": 400,
        "output_file": "抽样结果.xlsx",
        "columns_to_keep": ["序号", "用户编号", "水表表号"]
    }
    """
    try:
        # 清空文件夹
        clear_folder(input_dir)
        clear_folder(output_dir)

        # 保存上传文件
        saved_names = []
        for file in files:
            file_path = os.path.join(input_dir, file.filename)
            with open(file_path, "wb") as f:
                shutil.copyfileobj(file.file, f)
            saved_names.append(file.filename)

        if len(saved_names) < 1:
            return error_response("请上传数据文件", status_code=400)

        # 解析 options
        opts = {}
        if options:
            try:
                opts = json.loads(options)
            except Exception as e:
                return error_response(f"解析 options 失败：{e}", status_code=400)

        # 获取参数
        header_name = opts.get("header_name")
        filter_values = opts.get("filter_values", "")
        total_count = opts.get("total_count", 400)
        output_file = opts.get("output_file", "抽样结果.xlsx")
        columns_to_keep = opts.get("columns_to_keep")

        # 调用抽样函数
        out_path = sample_meter_list(
            input_dir=input_dir,
            output_dir=output_dir,
            file_name=saved_names[0],
            header_name=header_name,
            filter_values=filter_values,
            total_count=total_count,
            output_file=output_file,
            columns_to_keep=columns_to_keep,
        )

        return success_response(
            data=[os.path.basename(out_path)], message="数据抽样完成"
        )

    except ValueError as e:
        return error_response(str(e), status_code=400)
    except Exception as e:
        return error_response(f"服务器内部错误：{str(e)}", status_code=500)


@app.post("/split_by_header_enhanced")
async def split_by_header_enhanced_endpoint(
    files: list[UploadFile], options: str = Form(None)
):
    """
    增强版表头细分接口，支持拆分到不同文件或同一文件的不同sheet
    options JSON 示例：
    {
        "header_name": "抄表员",
        "split_to": "sheets",  # 或 "files"
        "extra_header": "备注",
        "extra_values": "待处理,已完成"
    }
    """
    try:
        clear_folder(input_dir)
        clear_folder(output_dir)

        saved = []
        for f in files:
            path = os.path.join(input_dir, f.filename)
            with open(path, "wb") as fh:
                shutil.copyfileobj(f.file, fh)
            saved.append(f.filename)

        if len(saved) < 1:
            return error_response("请上传要拆分的文件", status_code=400)

        opts = {}
        if options:
            try:
                opts = json.loads(options)
            except Exception as e:
                return error_response(f"解析 options 失败：{e}", status_code=400)

        header_name = opts.get("header_name")
        split_to = opts.get("split_to", "files")  # 默认拆分为文件
        extra_header = opts.get("extra_header")
        extra_values = opts.get("extra_values")

        if not header_name:
            return error_response("必须指定 header_name", status_code=400)

        extra_filters = None
        if extra_header:
            if extra_values is None:
                vals = [""]
            elif isinstance(extra_values, (list, tuple)):
                vals = [str(x) for x in extra_values]
            else:
                vals = [s for s in str(extra_values).split(",")]
            extra_filters = {extra_header: vals}

        out_paths = split_excel_by_header(
            input_dir=input_dir,
            output_dir=output_dir,
            header_name=header_name,
            extra_filters=extra_filters,
            file_name=saved[0],
            split_to=split_to,
            save_func=safe_save_excel,
        )

        names = [os.path.basename(p) for p in out_paths]
        return success_response(data=names, message="按表头拆分并导出完成")
    except ValueError as e:
        return error_response(str(e), status_code=400)
    except Exception as e:
        return error_response(f"服务器内部错误：{str(e)}", status_code=500)


@app.post("/format_survey_enhanced")
async def format_survey_enhanced_endpoint(
    files: list[UploadFile], options: str = Form(None)
):
    """
    增强版水表普查数据格式化接口
    options JSON 示例：
    {
        "title_text": "水表普查清单",
        "start_index": 0,
        "end_index": 100,
        "columns_to_keep": ["用户编号", "水表表号", "地址"]
    }
    """
    try:
        clear_folder(input_dir)
        clear_folder(output_dir)

        for file in files:
            file_path = os.path.join(input_dir, file.filename)
            with open(file_path, "wb") as f:
                shutil.copyfileobj(file.file, f)

        opts = {}
        if options:
            try:
                opts = json.loads(options)
            except Exception as e:
                return error_response(f"解析 options 失败：{e}", status_code=400)

        title_text = opts.get("title_text", "水表普查清单")
        start_index = opts.get("start_index", 0)
        end_index = opts.get("end_index")
        columns_to_keep = opts.get("columns_to_keep")

        out_files = format_survey_files(
            input_dir=input_dir,
            output_dir=output_dir,
            title_text=title_text,
            start_index=start_index,
            end_index=end_index,
            columns_to_keep=columns_to_keep,
            save_func=safe_save_excel,
        )

        out_names = [os.path.basename(p) for p in out_files]
        return success_response(data=out_names, message="水表普查数据格式化完成")
    except ValueError as e:
        return error_response(str(e), status_code=400)
    except Exception as e:
        return error_response(f"服务器内部错误：{str(e)}", status_code=500)


# ---------- 原有接口 ----------


@app.post("/upload")
async def upload_files(
    files: list[UploadFile],
    method: str = Form(""),
    options: str = Form(None),
):
    """上传并处理文件，可选择不同处理方法；options 为可选的 JSON 字符串配置（例如用于 format_survey_files）"""
    try:
        # 清空文件夹
        clear_folder(input_dir)
        clear_folder(output_dir)

        # 保存上传文件
        for file in files:
            file_path = os.path.join(input_dir, file.filename)
            with open(file_path, "wb") as f:
                shutil.copyfileobj(file.file, f)

        # 解析 options（如果有）
        opts = {}
        if options:
            import json

            try:
                opts = json.loads(options)
                if not isinstance(opts, dict):
                    raise ValueError("options 必须是 JSON 对象")
            except Exception as e:
                return error_response(f"解析 options 失败：{e}", status_code=400)

        # 根据 method 调用不同处理方法（兼容 format_survey_files）
        if method in ("format_survey_files", "format"):
            name = opts.get("name", opts.get("姓名", "姓名"))

            try:
                start_index = int(opts.get("start_index", opts.get("开始索引", 1)))
                end_index = int(opts.get("end_index", opts.get("结束索引", 900)))
            except Exception:
                return error_response(
                    "start_index/end_index 必须为整数", status_code=400
                )

            out_files = format_survey_files(
                input_dir,
                output_dir,
                name=name,
                start_index=start_index,
                end_index=end_index,
                save_func=safe_save_excel,
            )
            out_names = [os.path.basename(p) for p in out_files]
            return success_response(data=out_names, message="格式化并导出完成")
        else:
            return error_response(f"未找到方法: {method}", status_code=400)
    except ValueError as e:
        return error_response(str(e), status_code=400)
    except Exception as e:
        return error_response(f"服务器内部错误：{str(e)}", status_code=500)


@app.post("/upload_meter")
async def upload_meter(
    templateFile: UploadFile = File(...),
    remoteFile: UploadFile | None = File(None),
    computerFiles: list[UploadFile] | None = File(None),
):
    """
    通过水表表号录入：
    - 上传模板文件（templateFile）【必传】
    - 上传远程文件（remoteFile）【可选】
    - 上传多个电脑文件（computerFiles）【可选】
    """
    try:
        # 每次上传前清空 input 与 output
        clear_folder(input_dir)
        clear_folder(output_dir)

        # --- 保存上传文件 ---
        def save_upload(upload_file: UploadFile, name: str):
            path = os.path.join(input_dir, name)
            with open(path, "wb") as f:
                shutil.copyfileobj(upload_file.file, f)
            return path

        # 模板是必传
        if not templateFile:
            return error_response("必须上传模板文件 templateFile")

        save_upload(templateFile, templateFile.filename)

        # 可选文件存在时才保存
        if remoteFile is not None:
            save_upload(remoteFile, remoteFile.filename)

        if computerFiles:
            for f in computerFiles:
                save_upload(f, f.filename)

        # --- 调用处理逻辑 ---
        output_path = process_meter_files(
            input_dir=input_dir,
            output_dir=output_dir,
            save_func=safe_save_excel,
        )

        # --- 返回统一响应 ---
        return success_response(
            data=[os.path.basename(output_path)],
            message="水表表号录入处理完成",
        )

    except ValueError as e:
        return error_response(str(e), status_code=400)
    except Exception as e:
        return error_response(f"服务器内部错误：{str(e)}", status_code=500)


@app.get("/preview")
async def preview_result():
    """返回处理结果预览（取 output 目录下第一个 Excel 文件）"""
    try:
        files = [
            f for f in os.listdir(output_dir) if f.lower().endswith((".xlsx", ".xls"))
        ]
        if not files:
            return error_response("没有找到处理结果文件", status_code=404)
        # 按名称排序并取第一个（如需按时间可改为按 mtime 排序）
        files.sort()
        result_file = os.path.join(output_dir, files[0])

        excel_data = pd.read_excel(result_file, sheet_name=None)
        preview = {}
        for sheet, df in excel_data.items():
            # NaN、Inf 转 None
            df = df.replace({np.nan: None, np.inf: None, -np.inf: None})
            df = df.astype(object)
            preview[sheet] = df.to_dict(orient="records")
        return success_response(data=preview, message="预览数据加载成功")
    except Exception as e:
        return error_response(f"预览失败：{str(e)}", status_code=500)


@app.get("/download")
async def download_result():
    """下载 output 目录中的所有结果文件（若有多个则自动打包 zip）"""
    try:
        # 取所有文件（不限扩展名，灵活）
        files = [
            f
            for f in os.listdir(output_dir)
            if os.path.isfile(os.path.join(output_dir, f))
        ]
        if not files:
            return error_response("没有找到可下载的文件", status_code=404)

        # 如果只有一个文件，直接下载
        if len(files) == 1:
            result_file = os.path.join(output_dir, files[0])
            filename = urllib.parse.quote(files[0])

            def iterfile():
                with open(result_file, "rb") as f:
                    yield from f

            return StreamingResponse(
                iterfile(),
                media_type="application/octet-stream",
                headers={
                    "Content-Disposition": f"attachment; filename*=UTF-8''{filename}"
                },
            )

        # ---- 否则打包 ZIP ----
        zip_name = "全部处理结果.zip"
        zip_path = os.path.join(output_dir, zip_name)

        import zipfile

        with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as z:
            for f in files:
                z.write(os.path.join(output_dir, f), arcname=f)

        quoted_name = urllib.parse.quote(zip_name)

        def iterzip():
            with open(zip_path, "rb") as f:
                yield from f

        return StreamingResponse(
            iterzip(),
            media_type="application/zip",
            headers={
                "Content-Disposition": f"attachment; filename*=UTF-8''{quoted_name}"
            },
        )

    except Exception as e:
        return error_response(f"下载失败：{str(e)}", status_code=500)


@app.post("/compare")
async def compare_files_endpoint(files: list[UploadFile], options: str = Form(None)):
    """
    上传两个（或多个）文件并根据传入 key_column 对比：
    options JSON 示例：
      {"key_column":"用户编号", "old_file":"表2.xlsx", "new_file":"表1.xlsx", "output_file":"结果.xlsx"}
    如果 old_file/new_file 未指定，则使用上传的前两个文件（按上传顺序）。
    返回生成的文件名（相对 output 目录）。
    """
    try:
        # 清空文件夹
        clear_folder(input_dir)
        clear_folder(output_dir)

        # 保存上传文件（保持原名）
        saved_names = []
        for f in files:
            path = os.path.join(input_dir, f.filename)
            with open(path, "wb") as fh:
                shutil.copyfileobj(f.file, fh)
            saved_names.append(f.filename)

        # 解析 options
        opts = {}
        if options:
            try:
                opts = json.loads(options)
                if not isinstance(opts, dict):
                    return error_response("options 必须为 JSON 对象", status_code=400)
            except Exception as e:
                return error_response(f"解析 options 失败：{e}", status_code=400)

        key_column = opts.get("key_column") or opts.get("key") or "ID"
        old_file = opts.get("old_file") or opts.get("old")  # 需要被过滤的表
        new_file = opts.get("new_file") or opts.get("new")  # 参考表（已存在则剔除）

        # 如果未指定文件名，至少要上传两个文件
        if not old_file or not new_file:
            if len(saved_names) < 2:
                return error_response(
                    "未指定 old_file/new_file，且上传文件不足两个", status_code=400
                )
            # 默认：第一个为 old，第二个为 new
            old_file = old_file or saved_names[0]
            new_file = new_file or saved_names[1]

        output_file = opts.get("output_file", f"filtered_{old_file}")

        # 调用对比函数
        out_path = filter_excel_by_column(
            input_dir=input_dir,
            output_dir=output_dir,
            key_column=key_column,
            old_file_name=old_file,
            new_file_name=new_file,
            output_file=output_file,
        )

        # 返回文件名（相对 output 目录）
        return success_response(
            data=[os.path.basename(out_path)], message="对比并导出完成"
        )
    except ValueError as e:
        return error_response(str(e), status_code=400)
    except Exception as e:
        return error_response(f"服务器内部错误：{str(e)}", status_code=500)


@app.post("/sync_remarks")
async def sync_remarks_endpoint(files: list[UploadFile], options: str = Form(None)):
    """
    上传两个文件并同步备注：
    - 上传两个文件（第一个为主文件，第二个为副文件）
    - options JSON 可包含：
      {"key_columns":"ID,用户编号", "remark_column":"备注", "output_file":"结果.xlsx"}
    返回生成的文件名（relative to output dir）
    """
    try:
        clear_folder(input_dir)
        clear_folder(output_dir)

        saved = []
        for f in files:
            path = os.path.join(input_dir, f.filename)
            with open(path, "wb") as fh:
                shutil.copyfileobj(f.file, fh)
            saved.append(f.filename)

        # 解析 options
        opts = {}
        if options:
            try:
                opts = json.loads(options)
            except Exception as e:
                return error_response(f"解析 options 失败：{e}", status_code=400)

        key_columns = (
            opts.get("key_columns") or opts.get("key_column") or opts.get("key") or "ID"
        )
        remark_column = opts.get("remark_column") or opts.get("备注") or "备注"
        main_file = saved[0] if len(saved) >= 1 else None
        sub_file = saved[1] if len(saved) >= 2 else None
        if not main_file or not sub_file:
            return error_response(
                "请上传两个文件（主文件 和 副文件）用于同步备注", status_code=400
            )

        output_file = opts.get("output_file")  # 可为 None

        out_path = sync_remarks(
            input_dir=input_dir,
            output_dir=output_dir,
            key_columns=key_columns,
            remark_column=remark_column,
            main_file_name=main_file,
            sub_file_name=sub_file,
            output_file=output_file,
            save_func=safe_save_excel,
        )

        return success_response(
            data=[os.path.basename(out_path)], message="同步备注完成"
        )
    except ValueError as e:
        return error_response(str(e), status_code=400)
    except Exception as e:
        return error_response(f"服务器内部错误：{str(e)}", status_code=500)


@app.post("/split_by_header")
async def split_by_header_endpoint(files: list[UploadFile], options: str = Form(None)):
    """
    单文件上传并按表头拆分：
    options JSON 示例：
      {"header_name":"抄表员", "extra_header":"备注", "extra_values":""}
    extra_values 为逗号分隔的值列表；若包含空字符串 "" 则匹配空/空白值。
    返回生成的文件名列表（相对 output 目录）。
    """
    try:
        clear_folder(input_dir)
        clear_folder(output_dir)

        saved = []
        for f in files:
            path = os.path.join(input_dir, f.filename)
            with open(path, "wb") as fh:
                shutil.copyfileobj(f.file, fh)
            saved.append(f.filename)

        if len(saved) < 1:
            return error_response("请上传要拆分的文件", status_code=400)

        opts = {}
        if options:
            try:
                opts = json.loads(options)
                if not isinstance(opts, dict):
                    return error_response("options 必须为 JSON 对象", status_code=400)
            except Exception as e:
                return error_response(f"解析 options 失败：{e}", status_code=400)

        header_name = opts.get("header_name") or opts.get("表头") or None
        if not header_name:
            return error_response(
                "必须指定 header_name（要按其拆分的表头）", status_code=400
            )

        extra_header = opts.get("extra_header")
        extra_values = opts.get("extra_values")
        extra_filters = None
        if extra_header:
            if extra_values is None:
                vals = [""]
            elif isinstance(extra_values, (list, tuple)):
                vals = [str(x) for x in extra_values]
            else:
                vals = [s for s in str(extra_values).split(",")]
            extra_filters = {extra_header: vals}

        out_paths = split_excel_by_header(
            input_dir=input_dir,
            output_dir=output_dir,
            header_name=header_name,
            extra_filters=extra_filters,
            file_name=saved[0],
        )

        names = [os.path.basename(p) for p in out_paths]
        return success_response(data=names, message="按表头拆分并导出完成")
    except ValueError as e:
        return error_response(str(e), status_code=400)
    except Exception as e:
        return error_response(f"服务器内部错误：{str(e)}", status_code=500)


@app.post("/sample_meter_list")
async def sample_meter_list_endpoint(
    files: list[UploadFile], options: str = Form(None)
):
    """
    抽查普表清单：
    - 上传用户清单文件
    - options JSON 示例：
      {
        "header_name": "抄表员",
        "filter_values": "张三,李四",
        "total_count": 400,
        "output_file": "抽查结果.xlsx"
      }
    返回生成的文件名（相对 output 目录）
    """
    try:
        # 清空文件夹
        clear_folder(input_dir)
        clear_folder(output_dir)

        # 保存上传文件
        saved_names = []
        for file in files:
            file_path = os.path.join(input_dir, file.filename)
            with open(file_path, "wb") as f:
                shutil.copyfileobj(file.file, f)
            saved_names.append(file.filename)

        if len(saved_names) < 1:
            return error_response("请上传用户清单文件", status_code=400)

        # 解析 options
        opts = {}
        if options:
            try:
                opts = json.loads(options)
                if not isinstance(opts, dict):
                    return error_response("options 必须为 JSON 对象", status_code=400)
            except Exception as e:
                return error_response(f"解析 options 失败：{e}", status_code=400)

        # 获取参数
        header_name = opts.get("header_name") or opts.get("筛选表头") or "抄表员"
        filter_values = opts.get("filter_values") or opts.get("筛选值") or ""
        total_count = opts.get("total_count") or opts.get("总条数") or 400
        output_file = opts.get("output_file") or "抽查普表清单.xlsx"

        # 验证参数
        if not filter_values:
            return error_response("必须指定筛选值", status_code=400)

        try:
            total_count = int(total_count)
            if total_count <= 0:
                return error_response("总条数必须大于0", status_code=400)
        except (ValueError, TypeError):
            return error_response("总条数必须为整数", status_code=400)

        # 调用抽样函数
        out_path = sample_meter_list(
            input_dir=input_dir,
            output_dir=output_dir,
            file_name=saved_names[0],
            header_name=header_name,
            filter_values=filter_values,
            total_count=total_count,
            output_file=output_file,
        )

        return success_response(
            data=[os.path.basename(out_path)],
            message=f"抽查普表清单完成，共抽取 {total_count} 条记录",
        )
    except ValueError as e:
        return error_response(str(e), status_code=400)
    except Exception as e:
        return error_response(f"服务器内部错误：{str(e)}", status_code=500)


@app.post("/filter_common_values")
async def filter_common_values_endpoint(
    files: list[UploadFile], options: str = Form(None)
):
    """
    对比两个Excel文件，筛选相同值的行：
    - 上传两个文件（第一个为要筛选的表，第二个为参考表）
    - options JSON 示例：
      {
        "header_name": "用户编号",
        "file1_name": "表1.xlsx",
        "file2_name": "表2.xlsx",
        "output_file": "相同数据结果.xlsx"
      }
    返回生成的文件名（相对 output 目录）
    """
    try:
        # 清空文件夹
        clear_folder(input_dir)
        clear_folder(output_dir)

        # 保存上传文件
        saved_names = []
        for file in files:
            file_path = os.path.join(input_dir, file.filename)
            with open(file_path, "wb") as f:
                shutil.copyfileobj(file.file, f)
            saved_names.append(file.filename)

        if len(saved_names) < 2:
            return error_response("请上传两个文件进行对比", status_code=400)

        # 解析 options
        opts = {}
        if options:
            try:
                opts = json.loads(options)
                if not isinstance(opts, dict):
                    return error_response("options 必须为 JSON 对象", status_code=400)
            except Exception as e:
                return error_response(f"解析 options 失败：{e}", status_code=400)

        # 获取参数
        header_name = opts.get("header_name") or opts.get("对比表头") or "用户编号"
        file1_name = opts.get("file1_name") or saved_names[0]
        file2_name = opts.get("file2_name") or saved_names[1]
        output_file = opts.get("output_file") or "相同数据筛选结果.xlsx"

        # 验证参数
        if not header_name:
            return error_response("必须指定对比表头", status_code=400)

        # 调用筛选函数
        out_path = filter_by_common_values(
            input_dir=input_dir,
            output_dir=output_dir,
            file1_name=file1_name,
            file2_name=file2_name,
            header_name=header_name,
            output_file=output_file,
        )

        return success_response(
            data=[os.path.basename(out_path)],
            message=f"相同数据筛选完成，共找到匹配记录",
        )
    except ValueError as e:
        return error_response(str(e), status_code=400)
    except Exception as e:
        return error_response(f"服务器内部错误：{str(e)}", status_code=500)


@app.post("/compare_header_consistency")
async def compare_header_consistency_endpoint(
    files: list[UploadFile], options: str = Form(None)
):
    """
    对比两个表中相同ID的行，检查指定表头数据是否一致：
    - 上传两个文件
    - options JSON 示例：
      {
        "match_header": "ID",
        "compare_header": "金额",
        "file1_name": "表1.xlsx",
        "file2_name": "表2.xlsx",
        "output_file": "数据不一致结果.xlsx"
      }
    返回生成的文件名（相对 output 目录）
    """
    try:
        # 清空文件夹
        clear_folder(input_dir)
        clear_folder(output_dir)

        # 保存上传文件
        saved_names = []
        for file in files:
            file_path = os.path.join(input_dir, file.filename)
            with open(file_path, "wb") as f:
                shutil.copyfileobj(file.file, f)
            saved_names.append(file.filename)

        if len(saved_names) < 2:
            return error_response("请上传两个文件进行对比", status_code=400)

        # 解析 options
        opts = {}
        if options:
            try:
                opts = json.loads(options)
                if not isinstance(opts, dict):
                    return error_response("options 必须为 JSON 对象", status_code=400)
            except Exception as e:
                return error_response(f"解析 options 失败：{e}", status_code=400)

        # 获取参数
        match_header = opts.get("match_header") or opts.get("匹配表头") or "ID"
        compare_header = opts.get("compare_header") or opts.get("对比表头") or "金额"
        file1_name = opts.get("file1_name") or saved_names[0]
        file2_name = opts.get("file2_name") or saved_names[1]
        output_file = opts.get("output_file") or "数据不一致结果.xlsx"

        # 验证参数
        if not match_header:
            return error_response("必须指定匹配表头", status_code=400)
        if not compare_header:
            return error_response("必须指定对比表头", status_code=400)

        # 调用对比函数
        out_path = compare_header_data_consistency(
            input_dir=input_dir,
            output_dir=output_dir,
            file1_name=file1_name,
            file2_name=file2_name,
            match_header=match_header,
            compare_header=compare_header,
            output_file=output_file,
        )

        return success_response(
            data=[os.path.basename(out_path)],
            message=f"数据一致性对比完成，共找到 {len(pd.read_excel(out_path)) if os.path.exists(out_path) else 0} 条不一致记录",
        )
    except ValueError as e:
        return error_response(str(e), status_code=400)
    except Exception as e:
        return error_response(f"服务器内部错误：{str(e)}", status_code=500)


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=9426)
